package com.hk.core.jdbc;

import com.hk.commons.query.page.QueryPage;
import com.hk.commons.query.page.SimpleQueryPage;
import com.hk.commons.util.*;
import com.hk.core.jdbc.core.CustomBeanPropertyRowMapper;
import com.hk.core.jdbc.core.HumpColumnMapRowMapper;
import com.hk.core.jdbc.core.namedparam.JdbcSqlParameterSourceUtils;
import com.hk.core.jdbc.dialect.Dialect;
import com.hk.core.jdbc.exception.NonUniqueResultException;
import com.hk.core.jdbc.inspect.SqlInspector;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import java.util.*;

/**
 * @author kevin
 * @date 2018-09-19 10:16
 */
@Slf4j
public final class JdbcSession {

    private final JdbcTemplate jdbcTemplate;

    private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    private final Dialect dialect;

    private final SqlInspector sqlInspector;

    public JdbcSession(NamedParameterJdbcTemplate namedParameterJdbcTemplate,
                       Dialect dialect, SqlInspector sqlInspector) {
        this.jdbcTemplate = namedParameterJdbcTemplate.getJdbcTemplate();
        this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
        this.dialect = dialect;
        this.sqlInspector = sqlInspector;
    }

    /**
     * Delete
     *
     * @param arguments arguments
     */
    public boolean delete(DeleteArguments arguments) {
        AssertUtils.isTrue(StringUtils.isNotEmpty(arguments.getFrom()), "delete From must not be null");
        var sql = new StringBuilder("DELETE FROM ").append(arguments.getFrom());
        var params = new ArrayList<>();
        var conditionSql = arguments.getConditions().toSqlString(params);
        if (StringUtils.isNotEmpty(conditionSql)) {
            sql.append(" WHERE ").append(conditionSql);
        }
        return jdbcTemplate.update(inspect(sql.toString()), params.toArray()) > 0;
    }

    /**
     * update
     *
     * @param arguments arguments
     */
    public boolean update(UpdateArguments arguments) {
        AssertUtils.isTrue(StringUtils.isNotEmpty(arguments.getFrom()), "update From must not be null");
        var sets = arguments.getSets();
        AssertUtils.isTrue(CollectionUtils.isNotEmpty(sets), "update From must not be null");
        var sql = new StringBuilder("UPDATE ").append(arguments.getFrom()).append(" SET ");
        var index = 1;
        var size = sets.size();
        var params = new ArrayList<>();
        for (var entry : sets.entrySet()) {
            sql.append(entry.getKey()).append(" = ?");
            params.add(entry.getValue());
            if (index < size) {
                sql.append(",");
            }
            index++;
        }
        var conditionSql = arguments.getConditions().toSqlString(params);
        if (StringUtils.isNotEmpty(conditionSql)) {
            sql.append(" WHERE ").append(conditionSql);
        }
        return jdbcTemplate.update(inspect(sql.toString()), params.toArray()) > 0;
    }

    /**
     * 更新
     *
     * @param sql       sql
     * @param arguments arguments
     */
    public boolean update(String sql, Map<String, ?> arguments) {
        return namedParameterJdbcTemplate.update(inspect(sql), arguments) > 0;
    }

    private String inspect(String sql) {
        return Objects.isNull(sqlInspector) ? sql : sqlInspector.inspect(sql);
    }

    /**
     * 批量更新
     *
     * @param sql  sql
     * @param args 参数
     * @return true or false
     */
    public boolean batchUpdate(String sql, List<Object[]> args) {
        return jdbcTemplate.batchUpdate(inspect(sql), args).length > 0;
    }

    /**
     * 批量更新
     *
     * @param sql  sql
     * @param args args
     * @param <T>  T
     * @return true
     */
    public <T> boolean batchUpdate(String sql, Collection<T> args) {
        int[] rowArray = namedParameterJdbcTemplate.batchUpdate(inspect(sql), JdbcSqlParameterSourceUtils.createBatch(args));
        log.debug("batchUpdate result:{}", JsonUtils.serialize(rowArray));
        return Arrays.stream(rowArray).anyMatch(item -> item > 0);
    }

    /**
     * 集合查询
     *
     * @param arguments        arguments
     * @param retrieveRowCount retrieveRowCount
     * @return {@link ListResult}
     */
    public ListResult<Map<String, Object>> queryForList(SelectArguments arguments, boolean retrieveRowCount) {
        return queryForList(arguments, retrieveRowCount, new HumpColumnMapRowMapper());
    }

    /**
     * 查询唯一
     *
     * @param arguments  arguments
     * @param returnType returnType
     * @return T
     */
    public <T> T queryForObject(SelectArguments arguments, Class<T> returnType) {
        var statement = SelectStatement.buildSelect(arguments);
        var rowMapper = CustomBeanPropertyRowMapper.newInstance(returnType);
        return jdbcTemplate.queryForObject(inspect(statement.selectSql().toString()), rowMapper,
                statement.parameters().toArray());
    }

    /**
     * count 查询
     *
     * @param arguments arguments
     * @return 记录数
     */
    public long queryForCount(SelectArguments arguments) {
        var statement = SelectStatement.buildSelect(arguments);
        return ObjectUtils.defaultIfNull(queryForScalar(statement.countSql().toString(), Long.class, statement.parameters().toArray()), 0L);
    }

    /**
     * 查询返回对象，支持驼峰命名的属性
     *
     * @param arguments        arguments
     * @param retrieveRowCount retrieveRowCount
     * @param returnType       returnType
     * @return {@link ListResult}
     */
    public <T> ListResult<T> queryForList(SelectArguments arguments, boolean retrieveRowCount, Class<T> returnType) {
        var rowMapper = CustomBeanPropertyRowMapper.newInstance(returnType);
        return queryForList(arguments, retrieveRowCount, rowMapper);
    }

    /**
     * 分页查询
     *
     * @param arguments  arguments
     * @param returnType returnType
     * @param <T>        <T>
     * @return {@link QueryPage}
     */
    public <T> QueryPage<T> queryForPage(SelectArguments arguments, Class<T> returnType) {
        var result = queryForList(arguments, true, returnType);
        return new SimpleQueryPage<>(result.getResult(), result.getTotalRowCount(), arguments.getStartRowIndex() + 1,
                arguments.getPageSize());
    }

    /**
     * 集合查询
     *
     * @param arguments        arguments
     * @param retrieveRowCount retrieveRowCount
     * @param rowMapper        rowMapper
     * @return {@link ListResult}
     */
    private <T> ListResult<T> queryForList(SelectArguments arguments, boolean retrieveRowCount,
                                           RowMapper<T> rowMapper) {
        var stmt = SelectStatement.buildSelect(arguments);
        var params = stmt.parameters().toArray();
        long rowCount = 0;
        if (retrieveRowCount) {// 如果是分页查询，先查询记录数，如果记录数为 0 ，直接返回，不再查询数据集
            rowCount = queryForScalar(stmt.countSql().toString(), Long.class, params);
            if (rowCount == 0) {
                return new ListResult<>(rowCount, new ArrayList<>());
            }
        }
        var queryResult = queryForList(stmt.selectSql().toString(), rowMapper,
                arguments.getStartRowIndex() * arguments.getPageSize(),
                arguments.getPageSize(), params);
        if (0 == rowCount) {
            rowCount = queryResult.size();
        }
        return new ListResult<>(rowCount, queryResult);
    }

    /**
     * 标量查询，如求COUNT、MAX、MIN 等
     */
    public <T> T queryForScalar(SelectArguments arguments, Class<T> returnType) {
        var statement = SelectStatement.buildSelect(arguments);
        return queryForScalar(statement.selectSql().toString(), returnType, statement.parameters().toArray());
    }

    /**
     * 标量查询，如求COUNT、MAX、MIN 等
     *
     * @param sql        sql
     * @param returnType returnType
     * @param args       args
     * @return T
     */
    private <T> T queryForScalar(String sql, Class<T> returnType, Object... args) {
        return jdbcTemplate.queryForObject(inspect(sql), returnType, args);
    }

    /**
     * 只查询一条记录
     * <p>
     * select fields from table_name where condition1 = ? and condition2 = ? limit 0 ,2
     * </p>
     *
     * @param arguments  arguments
     * @param returnType returnType
     * @param <T>        T
     * @return T
     * @throws NonUniqueResultException 查询返回多条记录时抛出异常
     */
    public <T> Optional<T> queryForOne(SelectArguments arguments, Class<T> returnType) {
        var rowMapper = CustomBeanPropertyRowMapper.newInstance(returnType);
        rowMapper.setConversionService(ConverterUtils.DEFAULT_CONVERSION_SERVICE);
        var stmt = SelectStatement.buildSelect(arguments);
        final var originalSql = stmt.selectSql().toString();
        var sql = dialect.getLimitSql(originalSql, 0, 2); // 分页两条,如果返回有多条记录,抛出异常
        var result = queryForList(sql, rowMapper, stmt.parameters().toArray());
        if (result.size() > 1) {
            log.error("查询结果不唯一,返回多条记录: SQL : {},args:{}", originalSql, stmt.parameters());
            throw new NonUniqueResultException(STR."查询结果不唯一,SQL:\{originalSql}");
        }
        return CollectionUtils.getFirstOrDefault(result);
    }

    private <T> List<T> queryForList(String sql, RowMapper<T> rowMapper, int offset, int rows, Object... args) {
        if (offset >= 0 && rows > 0) {
            sql = dialect.getLimitSql(sql, offset, rows);
        }
        return queryForList(sql, rowMapper, args);
    }

    private <T> List<T> queryForList(String sql, RowMapper<T> rowMapper, Object... args) {
        return jdbcTemplate.query(inspect(sql), rowMapper, args);
    }


}
